namespace Filters.Demo
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Windows.Forms;
    using AForge;
    using AForge.Imaging;
    using AForge.Imaging.Filters;
    using AForge.Math;
    using Filters.Demo.Core;
    using Filters.Demo.Core.FilterConfiguration;
    using Filters.Demo.CustomFilters;
    using Filters.Demo.DataAccess;
    using Filters.Demo.FilterForms;
    using Filters.Demo.MetricSpaces;
    using WeifenLuo.WinFormsUI;

    /// <summary>
    /// Summary description for ImageForm.
    /// </summary>
    public partial class ImageForm : Content, IImageFilter, IEdgePointsReceptor
    {
        private const int MinDistance = 5;

        private readonly IHost host;
        private readonly IList<Bitmap> previousImages = new List<Bitmap>();
        private readonly IList<FilterElement> filters = new List<FilterElement>();
        private readonly Dictionary<Point, EdgePoint> mapedPoints = new Dictionary<Point, EdgePoint>();
        private readonly Dictionary<Point, EdgePoint> contourPoints = new Dictionary<Point, EdgePoint>();

        private readonly IImageCatalogRepository repository;
        private readonly IMetricSpacesHelper metricSpacesHelper;

        private Bitmap currentImage;
        private float zoom = 1;
        private bool cropping;
        private bool dragging;
        private bool cuttingoff;
        private int cutXAxeSelected;
        private Point start;
        private Point end;
        private Point startW;
        private Point endW;
        private Point startContour;
        private Point endContour;

        public ImageForm(string fileName, IHost host, IImageCatalogRepository repository, IMetricSpacesHelper metricSpacesHelper)
        {
            this.host = host;
            this.repository = repository;
            this.metricSpacesHelper = metricSpacesHelper;
            FileStream stream = null;

            try
            {
                // read image to temporary memory stream (.NET locks any stream until bitmap is disposed,
                // so that is why this work around is required)
                stream = File.OpenRead(fileName);
                var memoryStream = new MemoryStream();

                var buffer = new byte[10000];
                while (true)
                {
                    var read = stream.Read(buffer, 0, 10000);

                    if (read == 0)
                    {
                        break;
                    }

                    memoryStream.Write(buffer, 0, read);
                }

                this.currentImage = (Bitmap)System.Drawing.Image.FromStream(memoryStream);

                // format image
                if (!AForge.Imaging.Image.IsGrayscale(this.currentImage) && (this.currentImage.PixelFormat != PixelFormat.Format24bppRgb))
                {
                    var temp = AForge.Imaging.Image.Clone(this.currentImage, PixelFormat.Format24bppRgb);
                    this.currentImage.Dispose();
                    this.currentImage = temp;
                }

                this.FileName = fileName;
            }
            catch (Exception)
            {
                throw new ApplicationException("Failed loading image");
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                    stream.Dispose();
                }
            }

            this.Init();
            this.RegisterMenuItems();
        }

        // Events
        public delegate void SelectionEventHandler(object sender, SelectionEventArgs e);

        public event EventHandler DocumentChanged;

        public event EventHandler ZoomChanged;

        public event SelectionEventHandler MouseImagePosition;

        public event SelectionEventHandler SelectionChanged;

        public event EventHandler<FilterEventArgs> FilterApplied;

        // PreviousImages property
        public IList<Bitmap> PreviousImages
        {
            get { return this.previousImages; }
        }

        // Filter property
        public IList<FilterElement> Filters
        {
            get { return this.filters; }
        }

        public Dictionary<Point, EdgePoint> MapedPoints
        {
            get { return this.mapedPoints; }
        }

        // CurrentImage property
        public Bitmap CurrentImage
        {
            get { return this.currentImage; }
        }

        // Zoom property
        public float Zoom
        {
            get { return this.zoom; }
        }

        // FileName property
        public string FileName
        {
            get;
            private set;
        }

        // Width property
        private int ImageWidth
        {
            get;
            set;
        }

        // Height property
        private int ImageHeight
        {
            get;
            set;
        }

        // Reload image from file
        public void Reload()
        {
            if (this.FileName != null)
            {
                this.previousImages.Clear();
                this.filters.Clear();

                try
                {
                    // load image
                    var newImage = (Bitmap)System.Drawing.Image.FromFile(this.FileName);

                    // Release current image
                    this.currentImage.Dispose();

                    // set document image to just loaded
                    this.currentImage = newImage;

                    // format image
                    AForge.Imaging.Image.FormatImage(ref this.currentImage);
                }
                catch (Exception)
                {
                    throw new ApplicationException("Failed reloading image");
                }

                // update
                this.UpdateNewImage();
                this.InvokeFilterApplied(new FilterEventArgs(this.Filters));
            }
        }

        public void Save(string outputPath)
        {
            if (!Directory.Exists(outputPath))
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The output directory {0} does not exist.", outputPath), "outputPath");
            }

            this.host.SaveCurrentImage(Path.Combine(outputPath, Path.GetFileName(this.FileName)));
        }

        // Apply filter on the image
        public void ApplyFilter(IFilter filter, FilterElement filterData)
        {
            var filterInfo = filter as IFilterInformation;
            if (filterInfo != null)
            {
                if (!filterInfo.FormatTransalations.ContainsKey(this.currentImage.PixelFormat))
                {
                    if (filterInfo.FormatTransalations.ContainsKey(PixelFormat.Format24bppRgb))
                    {
                        MessageBox.Show("The selected image processing routine may be applied to color image only.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    else
                    {
                        MessageBox.Show("The selected image processing routine may be applied to grayscale or binary image only.\n\nUse grayscale (and threshold filter if required) before.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }

                    return;
                }
            }

            try
            {
                // set wait cursor
                this.Cursor = Cursors.WaitCursor;

                // apply filter to the image
                this.previousImages.Add(this.currentImage);
                this.filters.Add(filterData);
                this.currentImage = filter.Apply(this.currentImage);

                // update
                this.UpdateNewImage();
                this.InvokeFilterApplied(new FilterEventArgs(this.Filters));
            }
            catch
            {
                MessageBox.Show("Error occured applying selected filter to the image", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                // restore cursor
                this.Cursor = Cursors.Default;
            }
        }

        // Paint image
        protected override void OnPaint(PaintEventArgs e)
        {
            if (this.currentImage != null)
            {
                var g = e.Graphics;
                var rc = ClientRectangle;
                var pen = new Pen(Color.FromArgb(0, 0, 0));

                var newWidth = (int)(this.ImageWidth * this.zoom);
                var newHeight = (int)(this.ImageHeight * this.zoom);
                var x = (rc.Width < newWidth) ? this.AutoScrollPosition.X : (rc.Width - newWidth) / 2;
                var y = (rc.Height < newHeight) ? this.AutoScrollPosition.Y : (rc.Height - newHeight) / 2;

                // draw rectangle around the image
                g.DrawRectangle(pen, x - 1, y - 1, newWidth + 1, newHeight + 1);

                // set nearest neighbor interpolation to avoid image smoothing
                g.InterpolationMode = InterpolationMode.NearestNeighbor;

                // draw image
                g.DrawImage(this.currentImage, x, y, newWidth, newHeight);

                if (this.cuttingoff)
                {
                    this.cutXAxeSelected = this.PointToClient(Cursor.Position).X;

                    pen = new Pen(Brushes.Red);
                    g.DrawLine(pen, this.cutXAxeSelected, y, this.cutXAxeSelected, y + newHeight);
                    this.cutXAxeSelected -= x;
                }

                pen.Dispose();
            }
        }

        // Mouse click
        protected override void OnClick(EventArgs e)
        {
            this.Focus();
        }

        // Normalize points so, that pt1 becomes top-left point of rectangle and pt2 becomes right-bottom
        private static void NormalizePoints(ref Point pt1, ref Point pt2)
        {
            var t1 = pt1;
            var t2 = pt2;

            pt1.X = Math.Min(t1.X, t2.X);
            pt1.Y = Math.Min(t1.Y, t2.Y);
            pt2.X = Math.Max(t1.X, t2.X);
            pt2.Y = Math.Max(t1.Y, t2.Y);
        }

        private static FilterElement CreateChannelFilteringFilterElement(IntRange redRange, IntRange greenRange, IntRange blueRange)
        {
            var filterData = new FilterElement
            {
                Name = "Channel Filtering",
                Type = typeof(ChannelFiltering).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement
            {
                Name = "RedRange",
                Type = typeof(IntRange).AssemblyQualifiedName,
                FilterParameterValues =
                    new Collection<FilterParameterValueElement>
                    {
                        new FilterParameterValueElement { Type = "int", Value = redRange.Min.ToString() },
                        new FilterParameterValueElement { Type = "int", Value = redRange.Max.ToString() }
                    }
            });
            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement
            {
                Name = "GreenRange",
                Type = typeof(IntRange).AssemblyQualifiedName,
                FilterParameterValues =
                    new Collection<FilterParameterValueElement>
                    {
                        new FilterParameterValueElement { Type = "int", Value = greenRange.Min.ToString() },
                        new FilterParameterValueElement { Type = "int", Value = greenRange.Max.ToString() }
                    }
            });
            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement
            {
                Name = "BlueRange",
                Type = typeof(IntRange).AssemblyQualifiedName,
                FilterParameterValues =
                    new Collection<FilterParameterValueElement>
                    {
                        new FilterParameterValueElement { Type = "int", Value = blueRange.Min.ToString() },
                        new FilterParameterValueElement { Type = "int", Value = blueRange.Max.ToString() }
                    }
            });

            return filterData;
        }

        private void InvokeFilterApplied(FilterEventArgs args)
        {
            var handler = this.FilterApplied;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        private void RegisterMenuItems()
        {
            this.InitializeMenuItems();

            this.host.RegisterMenuItem(this.imageItem);
            this.host.RegisterMenuItem(this.filtersItem);
            this.host.RegisterMenuItem(this.edgesItem);
        }

        // Init the document
        private void Init()
        {
            // init components
            this.InitializeComponent();

            // form style
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);

            // init scroll bars
            this.AutoScroll = true;

            this.UpdateSize();

            this.MouseImagePosition += this.ImageFormMouseImagePosition;
        }

        private void ImageFormMouseImagePosition(object sender, SelectionEventArgs e)
        {
            if ((e.Location.X != -1) && (e.Location.Y != -1) && this.cuttingoff)
            {
                this.Refresh();
            }
        }

        // Update document and notify client about changes
        private void UpdateNewImage()
        {
            // update size
            this.UpdateSize();

            // repaint
            this.Invalidate();

            // notify host
            if (this.DocumentChanged != null)
            {
                this.DocumentChanged(this, null);
            }
        }

        // Update document size 
        private void UpdateSize()
        {
            // image dimension
            this.ImageWidth = this.currentImage.Width;
            this.ImageHeight = this.currentImage.Height;

            // scroll bar size
            this.AutoScrollMinSize = new Size((int)(this.ImageWidth * this.zoom), (int)(this.ImageHeight * this.zoom));
        }

        // Check if the image is color RGB image
        private bool CheckIfColor(string filterName)
        {
            // check pixel format
            if (this.currentImage.PixelFormat != PixelFormat.Format24bppRgb)
            {
                MessageBox.Show(filterName + " can be applied to RGB images only.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return false;
            }

            return true;
        }

        // Check if the image is grayscale image
        private bool CheckIfGrayscale(string filterName)
        {
            // check pixel format
            if (this.currentImage.PixelFormat != PixelFormat.Format8bppIndexed)
            {
                MessageBox.Show(filterName + " can be applied to grayscale images only.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return false;
            }

            return true;
        }

        // on "Image" item popup
        private void ImageItemPopup(object sender, System.EventArgs e)
        {
            this.undoImageItem.Enabled = this.previousImages.Count > 0;
            this.cropImageItem.Checked = this.cropping;
        }

        // on "Edges" item popup
        private void EdgesItemPopup(object sender, System.EventArgs e)
        {
            this.cutoffImageItem.Checked = this.cuttingoff;
        }

        // Restore image to previous
        private void UndoImageItemClick(object sender, System.EventArgs e)
        {
            if (this.previousImages.Count > 0)
            {
                // release current image
                this.currentImage.Dispose();

                // restore
                this.currentImage = this.previousImages.Last();

                // remove last
                if (this.Filters.Last().Name == "Edge Detection")
                {
                    this.mapedPoints.Clear();
                }

                this.previousImages.Remove(this.currentImage);
                this.filters.RemoveAt(this.filters.Count - 1);

                // update
                this.UpdateNewImage();
                this.InvokeFilterApplied(new FilterEventArgs(this.Filters));
            }
        }

        // Update zoom factor
        private void UpdateZoom()
        {
            this.AutoScrollMinSize = new Size((int)(this.ImageWidth * this.zoom), (int)(this.ImageHeight * this.zoom));
            this.Invalidate();

            // notify host
            if (this.ZoomChanged != null)
            {
                this.ZoomChanged(this, null);
            }
        }

        // Zoom image
        private void ZoomItemClick(object sender, System.EventArgs e)
        {
            // get menu item text
            this.zoom = (float)((MenuItem)sender).Tag;

            this.UpdateZoom();
        }

        // Zoom In image
        private void ZoomIn()
        {
            var z = this.zoom * 1.5f;

            if (z <= 10)
            {
                this.zoom = z;
                this.UpdateZoom();
            }
        }

        // On "Image->Zoom->Zoom In" item click
        private void ZoomInImageItemClick(object sender, System.EventArgs e)
        {
            this.ZoomIn();
        }

        // Zoom Out image
        private void ZoomOut()
        {
            var z = this.zoom / 1.5f;

            if (z >= 0.05)
            {
                this.zoom = z;
                this.UpdateZoom();
            }
        }

        // On "Image->Zoom->Zoom out" item click
        private void ZoomOutImageItemClick(object sender, System.EventArgs e)
        {
            this.ZoomOut();
        }

        // Fit to size
        private void FitToScreen()
        {
            var rc = ClientRectangle;

            this.zoom = Math.Min((float)rc.Width / (this.ImageWidth + 2), (float)rc.Height / (this.ImageHeight + 2));

            this.UpdateZoom();
        }

        // On "Image->Zoom->Fit To Screen" item click
        private void ZoomFitImageItemClick(object sender, System.EventArgs e)
        {
            this.FitToScreen();
        }

        // Flip image
        private void FlipImageItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Rotate - Flip",
                Type = typeof(RotateFlipFilter).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "RotateFlipType", Type = typeof(RotateFlipType).AssemblyQualifiedName, Value = "RotateNoneFlipY" });

            this.ApplyFilter(new RotateFlipFilter(RotateFlipType.RotateNoneFlipY), filterData);
        }

        // Mirror image
        private void MirrorItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Rotate - Flip",
                Type = typeof(RotateFlipFilter).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "RotateFlipType", Type = typeof(RotateFlipType).AssemblyQualifiedName, Value = "RotateNoneFlipX" });

            this.ApplyFilter(new RotateFlipFilter(RotateFlipType.RotateNoneFlipX), filterData);
        }

        // Rotate image 90 degree
        private void RotateImageItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Rotate - Flip",
                Type = typeof(RotateFlipFilter).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "RotateFlipType", Type = typeof(RotateFlipType).AssemblyQualifiedName, Value = "Rotate90FlipNone" });

            this.ApplyFilter(new RotateFlipFilter(RotateFlipType.Rotate90FlipNone), filterData);
        }

        // Invert image
        private void InvertColorFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Invert(), new FilterElement { Name = "Invert", Type = typeof(Invert).AssemblyQualifiedName });
        }

        // Rotatet colors
        private void RotateColorFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new RotateChannels(), new FilterElement { Name = "Rotate Channels", Type = typeof(RotateChannels).AssemblyQualifiedName });
        }

        // Sepia image
        private void EpiaColorFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Sepia(), new FilterElement { Name = "Sepia", Type = typeof(Sepia).AssemblyQualifiedName });
        }

        // Grayscale image
        private void Grayscale()
        {
            this.ApplyFilter(new GrayscaleBT709(), new FilterElement { Name = "Grayscale BT.709", Type = typeof(GrayscaleBT709).AssemblyQualifiedName });
        }

        // On "Filter->Color->Grayscale"
        private void GrayscaleColorFiltersItemClick(object sender, System.EventArgs e)
        {
            this.Grayscale();
        }

        // Converts grayscale image to RGB
        private void ToRgbColorFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new GrayscaleToRGB(), new FilterElement { Name = "Grayscale to RGB", Type = typeof(GrayscaleToRGB).AssemblyQualifiedName });
        }

        // Remove green and blue channels
        private void RedColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 255);
            var greenRange = new IntRange(0, 0);
            var blueRange = new IntRange(0, 0);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Remove red and blue channels
        private void GreenColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 0);
            var greenRange = new IntRange(0, 255);
            var blueRange = new IntRange(0, 0);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Remove red and green channels
        private void BlueColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 0);
            var greenRange = new IntRange(0, 0);
            var blueRange = new IntRange(0, 255);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Remove green channel
        private void CyanColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 0);
            var greenRange = new IntRange(0, 255);
            var blueRange = new IntRange(0, 255);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Remove green channel
        private void MagentaColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 255);
            var greenRange = new IntRange(0, 0);
            var blueRange = new IntRange(0, 255);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Remove blue channel
        private void YellowColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var redRange = new IntRange(0, 255);
            var greenRange = new IntRange(0, 255);
            var blueRange = new IntRange(0, 0);

            this.ApplyFilter(new ChannelFiltering(redRange, greenRange, blueRange), CreateChannelFilteringFilterElement(redRange, greenRange, blueRange));
        }

        // Extract red channel of image
        private void ExtractRedColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Channel",
                Type = typeof(ExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.R.ToString() });

            this.ApplyFilter(new ExtractChannel(RGB.R), filterData);
        }

        // Extract green channel of image
        private void ExtractGreenColorFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Channel",
                Type = typeof(ExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.G.ToString() });

            this.ApplyFilter(new ExtractChannel(RGB.G), filterData);
        }

        // Extract blue channel of image
        private void ExtractRedBlueFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Channel",
                Type = typeof(ExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.B.ToString() });

            this.ApplyFilter(new ExtractChannel(RGB.B), filterData);
        }

        // Extract red channel of NRGB color space
        private void ExtractRedFromNrgbMenuItemClick(object sender, EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Normalized RGB Channel",
                Type = typeof(ExtractNormalizedRGBChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.R.ToString() });

            this.ApplyFilter(new ExtractNormalizedRGBChannel(RGB.R), filterData);
        }

        // Extract green channel of NRGB color space
        private void ExtractGreenFromNrgbMenuItemClick(object sender, EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Normalized RGB Channel",
                Type = typeof(ExtractNormalizedRGBChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.G.ToString() });

            this.ApplyFilter(new ExtractNormalizedRGBChannel(RGB.G), filterData);
        }

        // Extract blue channel of NRGB color space
        private void ExtractBlueFromNrgbMenuItemClick(object sender, EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "Extract Normalized RGB Channel",
                Type = typeof(ExtractNormalizedRGBChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = RGB.B.ToString() });

            this.ApplyFilter(new ExtractNormalizedRGBChannel(RGB.B), filterData);
        }

        // Adjust brighness using HSL
        private void Brightness()
        {
            if (this.CheckIfColor("Brightness filter using HSL color space"))
            {
                var form = new BrightnessForm();
                form.Image = this.currentImage;

                if (form.ShowDialog() == DialogResult.OK)
                {
                    this.ApplyFilter(form.Filter, form.FilterData);
                }
            }
        }

        // On "Filters->HSL Color space->Brighness" menu item click
        private void BrightnessHslFiltersItemClick(object sender, System.EventArgs e)
        {
            this.Brightness();
        }

        // Modify contrast
        private void Contrast()
        {
            if (this.CheckIfColor("Contrast filter using HSL color space"))
            {
                var form = new ContrastForm();
                form.Image = this.currentImage;

                if (form.ShowDialog() == DialogResult.OK)
                {
                    this.ApplyFilter(form.Filter, form.FilterData);
                }
            }
        }

        // On "Filters->HSL Color space->Contrast" menu item click
        private void ContrastHslFiltersItemClick(object sender, System.EventArgs e)
        {
            this.Contrast();
        }

        // Adjust saturation using HSL
        private void Saturation()
        {
            if (this.CheckIfColor("Saturation filter using HSL color space"))
            {
                var form = new SaturationForm();
                form.Image = this.currentImage;

                if (form.ShowDialog() == DialogResult.OK)
                {
                    this.ApplyFilter(form.Filter, form.FilterData);
                }
            }
        }

        // On "Filters->HSL Color space->Saturation" menu item click
        private void SaturationHslFiltersItemClick(object sender, System.EventArgs e)
        {
            this.Saturation();
        }

        // Extract Y channel of YCbCr color space
        private void ExtracYFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "YCbCr Extract Channel",
                Type = typeof(YCbCrExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = YCbCr.YIndex.ToString() });

            this.ApplyFilter(new YCbCrExtractChannel(YCbCr.YIndex), filterData);
        }

        // Extract Cb channel of YCbCr color space
        private void ExtracCbFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "YCbCr Extract Channel",
                Type = typeof(YCbCrExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = YCbCr.CbIndex.ToString() });

            this.ApplyFilter(new YCbCrExtractChannel(YCbCr.CbIndex), filterData);
        }

        // Extract Cr channel of YCbCr color space
        private void ExtracCrFiltersItemClick(object sender, System.EventArgs e)
        {
            var filterData = new FilterElement
            {
                Name = "YCbCr Extract Channel",
                Type = typeof(YCbCrExtractChannel).AssemblyQualifiedName
            };

            filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement { Name = "Channel", Type = "short", Value = YCbCr.CrIndex.ToString() });

            this.ApplyFilter(new YCbCrExtractChannel(YCbCr.CrIndex), filterData);
        }

        // Threshold binarization
        private void Threshold()
        {
            if (this.CheckIfGrayscale("Threshold filter"))
            {
                var form = new ThresholdForm();

                // set image to preview
                form.Image = this.currentImage;

                if (form.ShowDialog() == DialogResult.OK)
                {
                    this.ApplyFilter(form.Filter, form.FilterData);
                }
            }
        }

        // On "Filters->Binarization->Threshold" menu item click
        private void ThresholdBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.Threshold();
        }

        // Threshold binarization with carry
        private void ThresholdCarryBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new ThresholdWithCarry(), new FilterElement { Name = "Threshold with Carry", Type = typeof(ThresholdWithCarry).AssemblyQualifiedName });
        }

        // Ordered dithering
        private void OrderedDitherBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new OrderedDithering(), new FilterElement { Name = "Ordered Dithering", Type = typeof(OrderedDithering).AssemblyQualifiedName });
        }

        // Bayer ordered dithering
        private void BayerDitherBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new BayerDithering(), new FilterElement { Name = "Bayer Dithering", Type = typeof(BayerDithering).AssemblyQualifiedName });
        }

        // Binarization using Floyd-Steinverg dithering algorithm
        private void FloydBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new FloydSteinbergDithering(), new FilterElement { Name = "Floyd-Steinberg Dithering", Type = typeof(FloydSteinbergDithering).AssemblyQualifiedName });
        }

        // Binarization using Burkes dithering algorithm
        private void BurkesBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new BurkesDithering(), new FilterElement { Name = "Burkes Dithering", Type = typeof(BurkesDithering).AssemblyQualifiedName });
        }

        // Binarization using Stucki dithering algorithm
        private void StuckiBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new StuckiDithering(), new FilterElement { Name = "Stucki Dithering", Type = typeof(StuckiDithering).AssemblyQualifiedName });
        }

        // Binarization using Jarvis, Judice and Ninke dithering algorithm
        private void JarvisBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new JarvisJudiceNinkeDithering(), new FilterElement { Name = "Jarvis-Judice-Ninke Dithering", Type = typeof(JarvisJudiceNinkeDithering).AssemblyQualifiedName });
        }

        // Binarization using Sierra dithering algorithm
        private void SierraBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new SierraDithering(), new FilterElement { Name = "Sierra Dithering", Type = typeof(SierraDithering).AssemblyQualifiedName });
        }

        // Threshold using Simple Image Statistics
        private void SisThresholdBinaryFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new SISThreshold(), new FilterElement { Name = "Simple Image Statistics Threshold", Type = typeof(SISThreshold).AssemblyQualifiedName });
        }

        // Otsu threshold
        private void OtsuThresholdMenuItemClick(object sender, EventArgs e)
        {
            this.ApplyFilter(new OtsuThreshold(), new FilterElement { Name = "Otsu Threshold", Type = typeof(OtsuThreshold).AssemblyQualifiedName });
        }

        // Errosion (Mathematical Morphology)
        private void ErosionMorphologyFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Erosion(), new FilterElement { Name = "Erosion", Type = typeof(Erosion).AssemblyQualifiedName });
        }

        // Dilatation (Mathematical Morphology)
        private void DilatationMorphologyFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Dilatation(), new FilterElement { Name = "Dilatation", Type = typeof(Dilatation).AssemblyQualifiedName });
        }

        // Opening (Mathematical Morphology)
        private void OpeningMorphologyFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Opening(), new FilterElement { Name = "Opening", Type = typeof(Opening).AssemblyQualifiedName });
        }

        // Closing (Mathematical Morphology)
        private void ClosingMorphologyFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Closing(), new FilterElement { Name = "Closing", Type = typeof(Closing).AssemblyQualifiedName });
        }

        // Top-hat (Mathematical Morphology)
        private void TophatMorphologyFiltersItemClick(object sender, EventArgs e)
        {
            this.ApplyFilter(new TopHat(), new FilterElement { Name = "Top Hat", Type = typeof(TopHat).AssemblyQualifiedName });
        }

        // Bottom-hat (Mathematical Morphology)
        private void BottomMorphologyFiltersItemClick(object sender, EventArgs e)
        {
            this.ApplyFilter(new BottomHat(), new FilterElement { Name = "Bottom Hat", Type = typeof(BottomHat).AssemblyQualifiedName });
        }

        // Mean
        private void MeanConvolutionFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Mean(), new FilterElement { Name = "Mean", Type = typeof(Mean).AssemblyQualifiedName });
        }

        // Blur
        private void BlurConvolutionFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Blur(), new FilterElement { Name = "Blur", Type = typeof(Blur).AssemblyQualifiedName });
        }

        // Sharpen
        private void SharpenConvolutionFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Sharpen(), new FilterElement { Name = "Sharpen", Type = typeof(Sharpen).AssemblyQualifiedName });
        }

        // Edges
        private void EdgesConvolutionFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Edges(), new FilterElement { Name = "Edges", Type = typeof(Edges).AssemblyQualifiedName });
        }

        // Adaptive smoothing
        private void AdaptiveSmoothingFiltersItemClick(object sender, System.EventArgs e)
        {
            var form = new AdaptiveSmoothForm();
            form.Image = this.currentImage;

            if (form.ShowDialog() == DialogResult.OK)
            {
                this.ApplyFilter(form.Filter, form.FilterData);
            }
        }

        // Conservative smoothing
        private void ConservativeSmoothingFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new ConservativeSmoothing(), new FilterElement { Name = "Conservative Smoothing", Type = typeof(ConservativeSmoothing).AssemblyQualifiedName });
        }

        // Contrast stretch filter
        private void ContrastStretchMenuItemClick(object sender, EventArgs e)
        {
            this.ApplyFilter(new ContrastStretch(), new FilterElement { Name = "Contrast Stretch", Type = typeof(ContrastStretch).AssemblyQualifiedName });
        }

        // Histogram equalization filter
        private void HistogramEqualizationMenuItemClick(object sender, EventArgs e)
        {
            this.ApplyFilter(new HistogramEqualization(), new FilterElement { Name = "Histogram Equalization", Type = typeof(HistogramEqualization).AssemblyQualifiedName });
        }

        // Median filter
        private void MedianFiltersItemClick(object sender, System.EventArgs e)
        {
            this.ApplyFilter(new Median(), new FilterElement { Name = "Median", Type = typeof(Median).AssemblyQualifiedName });
        }

        // Gamma correction
        private void GammaFiltersItemClick(object sender, System.EventArgs e)
        {
            var form = new GammaForm();

            form.Image = this.currentImage;

            if (form.ShowDialog() == DialogResult.OK)
            {
                this.ApplyFilter(form.Filter, form.FilterData);
            }
        }

        // Calculate image and screen coordinates of the point
        private void GetImageAndScreenPoints(Point point, out Point imgPoint, out Point screenPoint)
        {
            var rc = this.ClientRectangle;
            var newWidth = (int)(this.ImageWidth * this.zoom);
            var newHeight = (int)(this.ImageHeight * this.zoom);
            var x = (rc.Width < newWidth) ? this.AutoScrollPosition.X : (rc.Width - newWidth) / 2;
            var y = (rc.Height < newHeight) ? this.AutoScrollPosition.Y : (rc.Height - newHeight) / 2;

            var ix = Math.Min(Math.Max(x, point.X), x + newWidth - 1);
            var iy = Math.Min(Math.Max(y, point.Y), y + newHeight - 1);

            ix = (int)((ix - x) / this.zoom);
            iy = (int)((iy - y) / this.zoom);

            // image point
            imgPoint = new Point(ix, iy);

            // screen point
            screenPoint = this.PointToScreen(new Point((int)((ix * this.zoom) + x), (int)((iy * this.zoom) + y)));
        }

        // Draw selection rectangle
        private void DrawSelectionFrame(Graphics g)
        {
            var sp = this.startW;
            var ep = this.endW;

            // Normalize points
            NormalizePoints(ref sp, ref ep);

            // Draw reversible frame
            ControlPaint.DrawReversibleFrame(new Rectangle(sp.X, sp.Y, ep.X - sp.X + 1, ep.Y - sp.Y + 1), Color.White, FrameStyle.Dashed);
        }

        // Crop the image
        private void Crop()
        {
            if (!this.cropping)
            {
                // turn on
                this.cropping = true;
                this.Cursor = Cursors.Cross;
            }
            else
            {
                // turn off
                this.cropping = false;
                this.Cursor = Cursors.Default;
            }
        }

        // On "Image->Crop" - turn on/off cropping mode
        private void CropImageItemClick(object sender, System.EventArgs e)
        {
            this.Crop();
        }

        // On mouse down
        private void ImageFormMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                // turn off cropping mode
                if (!this.dragging)
                {
                    this.cropping = false;
                    this.Cursor = Cursors.Default;
                }

                // turn off cuttingoff mode
                if (this.cuttingoff)
                {
                    this.cuttingoff = false;
                    this.Refresh();
                    this.Cursor = Cursors.Default;
                }
            }
            else if (e.Button == MouseButtons.Left)
            {
                if (this.cropping)
                {
                    // start dragging
                    this.dragging = true;

                    // set mouse capture
                    this.Capture = true;

                    // get selection start point
                    this.GetImageAndScreenPoints(new Point(e.X, e.Y), out this.start, out this.startW);

                    // end point is the same as start
                    this.end = this.start;
                    this.endW = this.startW;

                    // draw frame
                    var g = this.CreateGraphics();
                    this.DrawSelectionFrame(g);
                    g.Dispose();
                }

                if (this.cuttingoff)
                {
                    // stop cutting off
                    this.cuttingoff = false;
                    this.Cursor = Cursors.Default;

                    this.FindStartAndEndContourPoints();
                    this.GenerateContour();
                }
            }
        }

        private void FindStartAndEndContourPoints()
        {
            // select the points that are 5 pixels of distance from the X coordinate of the cutoff point
            var nearPoints = (from p in this.mapedPoints.Keys
                              where (Math.Abs(p.X - this.cutXAxeSelected) < MinDistance)
                              orderby Math.Abs(p.X - this.cutXAxeSelected)
                              orderby p.Y
                              select p).ToList();

            // the first point in the list is the one selected as startContour point
            this.startContour = nearPoints.FirstOrDefault();

            nearPoints.Remove(this.startContour);

            // for the endContour point it is selected the one which is far away from the startContour point regarding the Y coordinate
            this.endContour = (from p in nearPoints
                               where Math.Abs(p.Y - this.startContour.Y) == nearPoints.Max(pf => Math.Abs(pf.Y - this.startContour.Y))
                               select p)
                              .FirstOrDefault();
        }

        // On mouse up
        private void ImageFormMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (this.dragging)
            {
                // stop dragging and cropping
                this.dragging = this.cropping = false;

                // release capture
                this.Capture = false;

                // set default mouse pointer
                this.Cursor = Cursors.Default;

                // erase frame
                var g = this.CreateGraphics();
                this.DrawSelectionFrame(g);
                g.Dispose();

                // normalize start and end points
                NormalizePoints(ref this.start, ref this.end);

                // filter data
                var filterData = new FilterElement
                {
                    Name = "Crop",
                    Type = typeof(Crop).AssemblyQualifiedName
                };

                var width = this.end.X - this.start.X + 1;
                var height = this.end.Y - this.start.Y + 1;

                filterData.FilterParameters.FilterParameters.Add(new FilterParameterElement
                                                                     {
                                                                         Name = "Rectangle",
                                                                         Type = typeof(Rectangle).AssemblyQualifiedName,
                                                                         FilterParameterValues =
                                                                             new Collection<FilterParameterValueElement>
                                                                                 {
                                                                                     new FilterParameterValueElement { Type = "int", Value = this.start.X.ToString() },
                                                                                     new FilterParameterValueElement { Type = "int", Value = this.start.Y.ToString() },
                                                                                     new FilterParameterValueElement { Type = "int", Value = width.ToString() },
                                                                                     new FilterParameterValueElement { Type = "int", Value = height.ToString() }
                                                                                 }
                                                                     });

                // crop the image
                this.ApplyFilter(new Crop(new Rectangle(this.start.X, this.start.Y, width, height)), filterData);
            }
        }

        // On mouse move
        private void ImageFormMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (this.dragging)
            {
                var g = this.CreateGraphics();

                // erase frame
                this.DrawSelectionFrame(g);

                // get selection end point
                this.GetImageAndScreenPoints(new Point(e.X, e.Y), out this.end, out this.endW);

                // draw frame
                this.DrawSelectionFrame(g);

                g.Dispose();

                if (this.SelectionChanged != null)
                {
                    var sp = this.start;
                    var ep = this.end;

                    // normalize start and end points
                    NormalizePoints(ref sp, ref ep);

                    this.SelectionChanged(this, new SelectionEventArgs(sp, new Size(ep.X - sp.X + 1, ep.Y - sp.Y + 1)));
                }
            }
            else
            {
                if (this.MouseImagePosition != null)
                {
                    var rc = this.ClientRectangle;
                    var newWidth = (int)(this.ImageWidth * this.zoom);
                    var newHeight = (int)(this.ImageHeight * this.zoom);
                    var x = (rc.Width < newWidth) ? this.AutoScrollPosition.X : (rc.Width - newWidth) / 2;
                    var y = (rc.Height < newHeight) ? this.AutoScrollPosition.Y : (rc.Height - newHeight) / 2;

                    if ((e.X >= x) && (e.Y >= y) && (e.X < x + newWidth) && (e.Y < y + newHeight))
                    {
                        // mouse is over the image
                        this.MouseImagePosition(this, new SelectionEventArgs(new Point((int)((e.X - x) / this.zoom), (int)((e.Y - y) / this.zoom))));
                    }
                    else
                    {
                        // mouse is outside image region
                        this.MouseImagePosition(this, new SelectionEventArgs(new Point(-1, -1)));
                    }
                }
            }
        }

        // On mouse leave
        private void ImageFormMouseLeave(object sender, System.EventArgs e)
        {
            if ((!this.dragging) && (this.MouseImagePosition != null))
            {
                this.MouseImagePosition(this, new SelectionEventArgs(new Point(-1, -1)));
            }
        }

        private void ImageFormFormClosed(object sender, FormClosedEventArgs e)
        {
            this.host.UnregisterMenuItem(this.imageItem);
            this.host.UnregisterMenuItem(this.filtersItem);
            this.host.UnregisterMenuItem(this.edgesItem);
        }

        private void ImageFormVisibleChanged(object sender, EventArgs e)
        {
            if (!this.disposed && !this.host.Disposing)
            {
                this.imageItem.Visible = this.Visible;
                this.filtersItem.Visible = this.Visible;
                this.edgesItem.Visible = this.Visible;
                this.undoImageItem.Shortcut = this.Visible ? Shortcut.CtrlZ : Shortcut.None;
            }
        }

        private void ImageFormFormClosing(object sender, FormClosingEventArgs e)
        {
            this.VisibleChanged -= this.ImageFormVisibleChanged;
        }

        private void EdgeDetectionClick(object sender, EventArgs e)
        {
            this.DetectEdges();
        }

        private void CutoffImageItemClick(object sender, EventArgs e)
        {
            if (!this.cuttingoff)
            {
                // turn on
                this.cuttingoff = true;
                this.Cursor = Cursors.VSplit;
            }
            else
            {
                // turn off
                this.cuttingoff = false;
                this.Cursor = Cursors.Default;
                this.Refresh();
            }
        }

        private void DetectEdges()
        {
            var filterData = new FilterElement
            {
                Name = "Edge Detection",
                Type = typeof(SobelPointBasedFilter).AssemblyQualifiedName
            };

            this.ApplyFilter(new SobelPointBasedFilter(this), filterData);
        }

        private void GenerateContour()
        {
            var sourcePoints = new Dictionary<Point, EdgePoint>(this.mapedPoints);
            var windowSize = 2;

            var curPoint = this.startContour;
            var newPoint = this.startContour;
            var pointChanged = false;

            this.contourPoints.Clear();

            while ((!curPoint.Equals(this.endContour)) && (sourcePoints.Count > 0))
            {
                // - - - - X
                // - - - - X
                // - - O - X
                // - - - - X
                // - - - - X
                var i = curPoint.X + windowSize;
                var j = curPoint.Y - windowSize;
                while ((j <= curPoint.Y + windowSize) && (!pointChanged))
                {
                    if (sourcePoints.ContainsKey(new Point(i, j)))
                    {
                        this.contourPoints.Add(curPoint, sourcePoints[curPoint]);
                        sourcePoints.Remove(curPoint);
                        newPoint = new Point(i, j);
                        pointChanged = true;
                    }

                    j++;
                }

                // - - - - -
                // - - - - -
                // - - O - -
                // - - - - -
                // X X X X -
                if (!pointChanged)
                {
                    i = curPoint.X + windowSize - 1;
                    j = curPoint.Y + windowSize;
                    while ((i >= curPoint.X - windowSize) && (!pointChanged))
                    {
                        if (sourcePoints.ContainsKey(new Point(i, j)))
                        {
                            this.contourPoints.Add(curPoint, sourcePoints[curPoint]);
                            sourcePoints.Remove(curPoint);
                            newPoint = new Point(i, j);
                            pointChanged = true;
                        }

                        i--;
                    }
                }

                // - - - - -
                // X - - - -
                // X - O - -
                // X - - - -
                // - - - - -
                if (!pointChanged)
                {
                    i = curPoint.X - windowSize;
                    j = curPoint.Y + windowSize - 1;
                    while ((j >= curPoint.Y - windowSize + 1) && (!pointChanged))
                    {
                        if (sourcePoints.ContainsKey(new Point(i, j)))
                        {
                            this.contourPoints.Add(curPoint, sourcePoints[curPoint]);
                            sourcePoints.Remove(curPoint);
                            newPoint = new Point(i, j);
                            pointChanged = true;
                        }

                        j--;
                    }
                }

                if (!pointChanged)
                {
                    windowSize++;
                }
                else
                {
                    windowSize = 2;
                }

                curPoint = newPoint;
                pointChanged = false;
            }

            this.MirrorContour();

            ContourForm contourForm = null;
            try
            {
                contourForm = new ContourForm(this.contourPoints, this.CurrentImage, this.FileName, this.repository, this.metricSpacesHelper);
                if (contourForm.Height < (this.currentImage.Height + 100))
                {
                    contourForm.Height = this.currentImage.Height + 100;
                }

                if (contourForm.Width < (this.currentImage.Width + 225))
                {
                    contourForm.Width = this.currentImage.Width + 225;
                }
            }
            catch (ApplicationException ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            if (contourForm != null)
            {
                contourForm.ShowDialog();
                this.contourPoints.Clear();
            }
        }

        private void MirrorContour()
        {
            if (this.contourPoints.Count > 0)
            {
                // alignment of the contour on X axis for mirroring
                if (this.startContour.X < this.endContour.X)
                {
                    this.contourPoints.Add(this.endContour, this.mapedPoints[this.endContour]);
                    this.endContour = new Point(this.startContour.X, this.endContour.Y);
                    this.contourPoints.Add(this.endContour, new EdgePoint(this.endContour, 0, 0));
                }
                else if (this.startContour.X > this.endContour.X)
                {
                    this.endContour = new Point(this.startContour.X, this.endContour.Y);
                    this.contourPoints.Add(this.endContour, new EdgePoint(this.endContour, 0, 0));
                }

                // mirror contour points
                var mirroredPoints = new Dictionary<Point, EdgePoint>();
                foreach (var pair in this.contourPoints)
                {
                    // only mirror points displaced from the X axis with respect to the Start-End points
                    if ((!pair.Key.Equals(this.startContour)) && (!pair.Key.Equals(this.endContour)) && (this.startContour.X != pair.Key.X))
                    {
                        var mirroredPt = new Point(this.startContour.X + (this.startContour.X - pair.Key.X), pair.Key.Y);
                        mirroredPoints.Add(mirroredPt, new EdgePoint(mirroredPt, pair.Value.Magnitude, pair.Value.Direction));
                    }
                }

                // add mirrored points in order, starting after the endContour point, to generate a closed sequence
                foreach (var pair in mirroredPoints.Reverse())
                {
                    if (!this.contourPoints.Keys.Contains(pair.Key))
                    {
                        this.contourPoints.Add(pair.Key, pair.Value);
                    }
                }
            }
        }
    }
}
